package network;

import jdd.bdd.BDD;
import model.BDDEngine;
import org.jgrapht.alg.util.Pair;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Changes {
    private HashMap<Port, HashMap<Port, Integer>> oldToNewPort;
    private HashMap<Integer, ArrayList<Pair<Port, Port>>> bddToChanges;
    private BDDEngine bddEngine;

    public Changes(BDDEngine bddEngine) {
        this.bddEngine = bddEngine;
        oldToNewPort = new HashMap<>();
    }

    public void add(int deltaBdd, Port oldPort, Port newPort) {
        BDD bdd = bddEngine.bdd;
        if (!oldToNewPort.containsKey(oldPort)) oldToNewPort.put(oldPort, new HashMap<>());
        HashMap<Port, Integer> newPortToBdd = oldToNewPort.get(oldPort);
        if (newPortToBdd.containsKey(newPort)) {
            int oldBdd = newPortToBdd.get(newPort);
            int union = bdd.ref(bdd.or(oldBdd, deltaBdd));
            newPortToBdd.replace(newPort, union);
            bdd.deref(oldBdd);
        } else {
            newPortToBdd.put(newPort, deltaBdd);
            bdd.ref(deltaBdd);
        }
    }

    public HashMap<Integer, ArrayList<Pair<Port, Port>>> getAll() {
        return bddToChanges;
    }

    public void releaseBdd() {
        for (HashMap<Port, Integer> value : oldToNewPort.values()) {
            for (Integer bdd : value.values()) {
                bddEngine.bdd.deref(bdd);
            }
        }
    }

    public void aggrBDDs() {
        bddToChanges = new HashMap<>();
        for (Map.Entry<Port, HashMap<Port, Integer>> entryI : oldToNewPort.entrySet()){
            Port oldPort = entryI.getKey();
            for (Map.Entry<Port, Integer> entryJ : entryI.getValue().entrySet()) {
                Port newPort = entryJ.getKey();
                int bdd = entryJ.getValue();
                if (!bddToChanges.containsKey(bdd)) {
                    bddToChanges.put(bdd, new ArrayList<>());
                }
                bddToChanges.get(bdd).add(new Pair<>(oldPort, newPort));
            }
        }
    }

    public int size() {
        int ret = 0;
        for (HashMap<Port, Integer> value : oldToNewPort.values()) ret += value.size();
        return ret;
    }

    public int bdds() {
        HashSet<Integer> ret = new HashSet<>();
        for (HashMap<Port, Integer> value : oldToNewPort.values()) ret.addAll(value.values());
        return ret.size();
    }


    public void merge(Changes t) {
        for (Port oldPort : t.oldToNewPort.keySet()) {
            for (Map.Entry<Port, Integer> entry : t.oldToNewPort.get(oldPort).entrySet()) {
                this.add(entry.getValue(), oldPort, entry.getKey());
            }
        }
    }
}
